package lt.inkredibl.iit;

import java.awt.Color;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;


public class Contour
{
	private List<Point2D> _points = new ArrayList<Point2D>();

	private int[] _corners = {-1, -1, -1, -1};

	public Point2D findPoint(Point2D p, double prec)
	{
//		System.out.println("findPoint " + p + ", " + prec + ":");
		for (Point2D pt : _points)
		{
//			System.out.println("\t" + pt + ": " + pt.distance(p));
			if (pt.distance(p) <= prec)
			{
				return pt;
			}
		}
		return null;
	}

	public Line2D findLine(Point2D p, double prec)
	{
		if (_points.size() < 2)
		{
			return null;
		}
		Point2D pp = _points.get(_points.size() - 1);
		Line2D line = new Line2D.Double();
		for (Point2D cp : _points)
		{
			line.setLine(pp, cp);
			if (line.ptSegDist(p) <= prec)
			{
				return line;
			}
			pp = cp;
		}
		return null;
	}

	public Line2D findNearestLine(Point2D p)
	{
		if (_points.size() < 2)
		{
			return null;
		}
		Point2D pp = _points.get(_points.size() - 1);
		Line2D found = new Line2D.Double();
		double foundDist = -1;
		Line2D line = new Line2D.Double();
		for (Point2D cp : _points)
		{
			line.setLine(pp, cp);
			double cdist = line.ptSegDist(p);
			if (foundDist < 0)
			{
				foundDist = cdist;
				found.setLine(line);
			}
			else
			{
				if (cdist < foundDist)
				{
					found.setLine(line);
					foundDist = cdist;
				}
			}
			pp = cp;
		}
		return found;
	}

	public void addPoint(Point2D p)
	{
		if (_points.contains(p))
		{
			System.err.println("Contour already contains "+p);
			Thread.dumpStack();
		}
		else
		{
			_points.add(p);
		}
	}

	public void insertPointAfter(Point2D p, Point2D after)
	{
		if (_points.contains(p))
		{
			System.err.println("Contour already contains "+p);
			Thread.dumpStack();
		}
		else
		{
			_points.add(_points.indexOf(after) + 1, p);
		}
	}

	static class Vertex
		implements Comparable<Vertex>
	{
		private double _val;

		private int _idx;

		public Vertex(double val, int idx)
		{
			_val = val;
			_idx = idx;
		}

		public int getIndex()
		{
			return _idx;
		}

		public int compareTo(Vertex o)
		{
			if (_val > o._val)
			{
				return 1;
			}
			else if (_val < o._val)
			{
				return -1;
			}
			return 0;
		}

		@Override
		public boolean equals(Object obj)
		{
			if (obj instanceof Vertex)
			{
				Vertex v = (Vertex) obj;
				return v._val == _val;
			}
			return super.equals(obj);
		}

		public double getAngle()
		{
			return _val;
		}
	}
	
	public boolean cornersAvailable()
	{
		return _corners[0] != -1;
	}
	
	public void invalidateCorners()
	{
		for (int i = 0; i < 4; i++)
		{
			_corners[i] = -1;
		}
	}

	public void setCorner(int i, Point2D pt)
	{
		_corners[i] = _points.indexOf(pt);
	}
	
	public void findCorners()
	{
		System.out.println("findCorners");
		if (_points.size() > 3)
		{
			Vertex[] vertices = new Vertex[_points.size()];
			Point2D gp = _points.get(_points.size() - 2);
			Point2D pp = _points.get(_points.size() - 1);
			int i = -1;
			for (Point2D cp : _points)
			{
				double pa = pseudoAngle(gp, pp, cp);
				if (Double.isNaN(pa))
				{
					System.out.println("Angle is NaN "+gp+" "+pp+" "+cp);
				}
				
				if (i == -1)
				{
					vertices[0] = new Vertex(pa, _points.size() - 1);
				}
				else
				{
					vertices[i + 1] = new Vertex(pa, i);
				}
				// System.out.println(i+": "+pa);

				gp = pp;
				pp = cp;
				i++;
			}
			Arrays.sort(vertices);
			for (i = 0; i < 4; i++)
			{
				_corners[i] = vertices[i].getIndex();
				System.out.println(_corners[i]+"\t"+vertices[i].getAngle());
			}
		}
	}

	static boolean first = false;

	static void d(String str)
	{
		if (first)
			System.out.println(str);
	}

	public double pseudoAngle(Point2D p1, Point2D p2, Point2D p3)
	{
		d(p1 + ", " + p2 + ", " + p3);
		double x1 = p1.getX() - p2.getX();
		d("x1 = " + x1);
		double y1 = p1.getY() - p2.getY();
		d("y1 = " + y1);
		double len = Math.sqrt((x1 * x1) + (y1 * y1));
		d("len = " + len);
		x1 /= len;
		y1 /= len;
		double x2 = p3.getX() - p2.getX();
		d("x2 = " + x2);
		double y2 = p3.getY() - p2.getY();
		d("y2 = " + y2);
		len = Math.sqrt((x2 * x2) + (y2 * y2));
		d("len = " + len);
		x2 /= len;
		y2 /= len;

		x2 -= x1;
		y2 -= y1;

		d("x = " + x2 + ", y = " + y2);
		len = (x2 * x2) + (y2 * y2); // len^2
		d("result: " + len);
		first = false;
		return len;
	}

	public void paint(Grapher g)
	{
		Point2D pp = _points.size() > 1 ? _points.get(_points.size() - 1) : null;
		int i = 0;
		for (Point2D p : _points)
		{
			if (i == _corners[0] || i == _corners[1] || i == _corners[2] || i == _corners[3])
			{
				g.setColor(Color.RED);
			}
			g.drawNamedMarker(p, String.valueOf(i));
			g.setColor(Color.BLACK);
			if (pp != null)
			{
				g.drawLine(pp, p);
			}
			i++;
			pp = p;
		}
	}

	public void removePoint(Point2D p)
	{
		_points.remove(p);
		invalidateCorners();
	}

	public Properties getState()
	{
		Properties p = new Properties();
		int size = _points.size();
		p.put("size", String.valueOf(size));
		for (int i = 0; i < size; i++)
		{
			Point2D pt = _points.get(i);
			p.put("x" + i, String.valueOf(pt.getX()));
			p.put("y" + i, String.valueOf(pt.getY()));
		}
		return p;
	}

	public void setState(Properties p)
	{
		int size = Integer.parseInt(p.get("size").toString());
		clear();
		for (int i = 0; i < size; i++)
		{
			Point2D pt = new Point2D.Double(Double.parseDouble(p.get("x" + i).toString()), Double.parseDouble(p.get("y" + i).toString()));
			_points.add(i, pt);
		}
	}
	
	public Iterable<Line2D.Double> toIterable()
	{
		return new ContourIterable(_points);
	}

	public void insertPoint(Point2D p)
	{
		Line2D line = findNearestLine(p);
		if (line != null)
		{
			insertPointAfter(p, line.getP1());
		}
		else
		{
			addPoint(p);
		}
	}

	public Tetragon getTetragon()
	{
		if (!cornersAvailable())
		{
			findCorners();
		}
		Tetragon t = new Tetragon();
		List<Point2D> unused = new ArrayList<Point2D>();
		List<Point2D> edge = new ArrayList<Point2D>();
		boolean cornerFound = false;
		for (int i = 0; i < _points.size(); i++)
		{
			Point2D pt = _points.get(i);
			if (isCorner(i))
			{
				if (cornerFound)
				{
					edge.add(pt);
					t.addEdge(edge);
					edge = new ArrayList<Point2D>();
				}
				else
				{
					unused.add(pt);
					cornerFound = true;
				}
			}
			
			if (cornerFound)
			{
				edge.add(pt);
			}
			else
			{
				unused.add(pt);
			}
		}
		if (unused.size() > 0)
		{
			edge.addAll(unused);
			t.addEdge(edge);
		}
		return t;
	}

	private boolean isCorner(int i)
	{
		return _corners[0] == i || _corners[1] == i || _corners[2] == i || _corners[3] == i;
	}

	public void clear()
	{
		_points.clear();
	}
}
